package cn.zhangfusheng.elasticsearch.template;

import cn.zhangfusheng.elasticsearch.annotation.dsl.jpa.JpaSearch;
import cn.zhangfusheng.elasticsearch.annotation.dsl.jpa.JpaSearchs;
import cn.zhangfusheng.elasticsearch.constant.ElasticSearchConstant;
import cn.zhangfusheng.elasticsearch.constant.enumeration.SearchKeyword;
import cn.zhangfusheng.elasticsearch.exception.GlobalSystemException;
import cn.zhangfusheng.elasticsearch.model.jpa.JpaBetween;
import cn.zhangfusheng.elasticsearch.model.page.PageRequest;
import cn.zhangfusheng.elasticsearch.scan.ElasticSearchEntityRepositoryDetail;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author fusheng.zhang
 * @date 2021-09-10 12:22:47
 */
public interface TemplateJpaApi extends Template, ElasticSearchTemplateApi {

    default Object runJpa(
            ElasticSearchEntityRepositoryDetail entityRepositoryDetail,
            Method method, Object[] args, String routing, Class<?> entityClass, String index) {
        try {
            this.analysisJapSearchAnnotation(method);
            List<Object> params = Arrays.stream(args).collect(Collectors.toList());
            BoolQueryBuilder boolQueryBuilder =
                    this.buildBoolQueryWithJap(method, null, params, entityClass);
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(boolQueryBuilder);
            String[] indices = this.analysisIndex(method, args, index);
            SearchRequest searchRequest =
                    new SearchRequest().routing(routing).indices(indices).source(searchSourceBuilder);
            // 分页参数提取
            Optional<PageRequest> pageRequestOptional =
                    params.stream().filter(o -> o instanceof PageRequest).map(o -> (PageRequest) o).findFirst();
            return search(entityRepositoryDetail, method, searchRequest, pageRequestOptional.orElse(null));
        } catch (IOException e) {
            throw new GlobalSystemException(e);
        }
    }

    /**
     * 解析 JpaSearch annotation
     * @param method
     */
    default void analysisJapSearchAnnotation(Method method) {
        if (!ElasticSearchConstant.JPA_SEARCH_CACHE.containsKey(method)) {
            synchronized (ElasticSearchConstant.JPA_SEARCH_CACHE) {
                if (!ElasticSearchConstant.JPA_SEARCH_CACHE.containsKey(method)) {
                    JpaSearchs jpaSearchs = method.getAnnotation(JpaSearchs.class);
                    JpaSearch jpaSearch = method.getAnnotation(JpaSearch.class);
                    List<JpaSearch> jpaSearchList = new ArrayList<>();
                    if (Objects.nonNull(jpaSearchs) || Objects.nonNull(jpaSearch)) {
                        if (Objects.nonNull(jpaSearchs) && jpaSearchs.value().length > 0) {
                            jpaSearchList.addAll(Arrays.asList(jpaSearchs.value()));
                        }
                        if (Objects.nonNull(jpaSearch)) jpaSearchList.add(jpaSearch);
                        Map<String, SearchKeyword> jpaSearchConfig =
                                jpaSearchList.stream().collect(Collectors.toMap(JpaSearch::value, JpaSearch::searchKeyword));
                        ElasticSearchConstant.JPA_SEARCH_CACHE.put(method, jpaSearchConfig);
                    }
                }
            }
        }
    }


    /**
     * build bool query with jpa run
     * @param args
     */
    default BoolQueryBuilder buildBoolQueryWithJap(Method method,
            BoolQueryBuilder base, List<Object> args, Class<?> entityClass) {
        Iterator<Object> argsIterator = args.iterator();
        PartTree tree = new PartTree(method.getName(), entityClass);
        BoolQueryBuilder current, pre = null;
        for (PartTree.OrPart node : tree) {
            Iterator<Part> parts = node.iterator();
            if (parts.hasNext()) {
                Part part = parts.next();
                if (argsIterator.hasNext()) {
                    current = this.create(method, part, argsIterator.next());
                    while (parts.hasNext()) {
                        part = parts.next();
                        if (argsIterator.hasNext()) {
                            current = this.and(method, part, current, argsIterator.next());
                        }
                    }
                    base = base == null ? pre = current : this.or(base == pre ? QueryBuilders.boolQuery().should(pre) : base, pre = current);
                }
            }
        }
        return base;
    }

    default BoolQueryBuilder create(Method method, Part part, Object value) {
        return from(method, part, QueryBuilders.boolQuery(), value);
    }


    default BoolQueryBuilder and(Method method, Part part, BoolQueryBuilder base, Object value) {
        return this.from(method, part, base, value);
    }

    default BoolQueryBuilder or(BoolQueryBuilder base, BoolQueryBuilder query) {
        return base.should(query);
    }

    default BoolQueryBuilder from(Method method, Part part, BoolQueryBuilder boolQueryBuilder, Object parameter) {
        // TODO 待完善解析
        String key = part.getProperty().getSegment();
        Map<String, SearchKeyword> jpaSearchConfig = ElasticSearchConstant.JPA_SEARCH_CACHE.get(method);
        if (Objects.nonNull(jpaSearchConfig) && jpaSearchConfig.containsKey(key)) {
            SearchKeyword searchKeyword = jpaSearchConfig.get(key);
            return boolQueryBuilder.must(searchKeyword.getBuilder(key, parameter));
        }
        Part.Type type = part.getType();
        switch (type) {
            case TRUE:
                return boolQueryBuilder.must(QueryBuilders.termQuery(key, Boolean.TRUE));
            case FALSE:
                return boolQueryBuilder.must(QueryBuilders.termQuery(key, Boolean.FALSE));
            case NEGATING_SIMPLE_PROPERTY:
                return boolQueryBuilder.mustNot(QueryBuilders.termQuery(key, parameter));
            case LIKE:
                return boolQueryBuilder.must(QueryBuilders.matchQuery(key, parameter));
            case SIMPLE_PROPERTY:
            case REGEX:
            case STARTING_WITH:
            case ENDING_WITH:
                return boolQueryBuilder.must(QueryBuilders.termQuery(key, parameter));
            case CONTAINING:
                return boolQueryBuilder.must(QueryBuilders.matchPhraseQuery(key, parameter));
            case GREATER_THAN:
                return boolQueryBuilder.must(QueryBuilders.rangeQuery(key).gt(parameter));
            case AFTER:
            case GREATER_THAN_EQUAL:
                return boolQueryBuilder.must(QueryBuilders.rangeQuery(key).gte(parameter));
            case LESS_THAN:
                return boolQueryBuilder.must(QueryBuilders.rangeQuery(key).lt(parameter));
            case BEFORE:
            case LESS_THAN_EQUAL:
                return boolQueryBuilder.must(QueryBuilders.rangeQuery(key).lte(parameter));
            case BETWEEN:
                if (!(parameter instanceof JpaBetween)) {
                    throw new GlobalSystemException("使用 between时,与之对应的参数必须是 JpaBwtween ");
                }
                JpaBetween jpaBetween = (JpaBetween) parameter;
                RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(key);
                if (Objects.nonNull(jpaBetween.getGt())) rangeQueryBuilder.gt(jpaBetween.getGt());
                if (Objects.nonNull(jpaBetween.getLte())) rangeQueryBuilder.lte(jpaBetween.getLte());
                return boolQueryBuilder.must(rangeQueryBuilder);
            case IS_NULL:
                return boolQueryBuilder.mustNot(QueryBuilders.existsQuery(key));
            case IS_NOT_NULL:
                return boolQueryBuilder.must(QueryBuilders.existsQuery(key));
            case IN:
                if (!(parameter instanceof Collection)) {
                    throw new GlobalSystemException("in params not instanceof Collection");
                }
                return boolQueryBuilder.must(QueryBuilders.termsQuery(key, (Collection<?>) parameter));
            case NOT_IN:
                if (!(parameter instanceof Collection)) {
                    throw new GlobalSystemException("not in params not instanceof Collection");
                }
                return boolQueryBuilder.mustNot(QueryBuilders.termsQuery(key, (Collection<?>) parameter));
            case WITHIN:

            case NEAR:

            default:
                throw new RuntimeException("Illegal criteria found '" + type + "'.");
        }
    }

    default void buildSearch() {
    }
}
